////////////////////////////////////////////////////////////////////////////////////////////
//CodecManager.cpp
//
//Copyright 2010 Eric Callahan. All rights reserved.
//
//Redistribution and use in source and binary forms, with or without modification, are
//permitted provided that the following conditions are met:
//
//   1. Redistributions of source code must retain the above copyright notice, this list of
//      conditions and the following disclaimer.
//
//   2. Redistributions in binary form must reproduce the above copyright notice, this list
//      of conditions and the following disclaimer in the documentation and/or other materials
//      provided with the distribution.
//
//THIS SOFTWARE IS PROVIDED BY ERIC CALLAHAN ``AS IS'' AND ANY EXPRESS OR IMPLIED
//WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
//FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL ERIC CALLAHAN OR
//CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
//CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
//SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
//ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
//NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
//ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
//
//The views and conclusions contained in the software and documentation are those of the
//authors and should not be interpreted as representing official policies, either expressed
//or implied, of Eric Callahan.
////////////////////////////////////////////////////////////////////////////////////////////

#include "CodecManager.h"


CodecManager::CodecManager(void)
{
	// Create Filter list arrays, then initialize all to NULL
	m_vidDecs = new FilterIdent*[SUPPORTED_VID_FILTER_COUNT];
	m_audDecs = new FilterIdent*[SUPPORTED_AUD_FILTER_COUNT];
	m_capFilters = new FilterIdent*[SUPPORTED_CAP_FILTER_COUNT];

	for (int i=0; i < SUPPORTED_VID_FILTER_COUNT; i++)
	{
		m_vidDecs[i] = NULL;
	}

	for (int i=0; i < SUPPORTED_AUD_FILTER_COUNT; i++)
	{
		m_audDecs[i] = NULL;
	}

	for (int i=0; i < SUPPORTED_CAP_FILTER_COUNT; i++)
	{
		m_capFilters[i] = NULL;
	}

	// locate and enumerate machine codecs
	EnumerateSupportedCodecs();
	EnumerateCaptureDevice();
}


CodecManager::~CodecManager(void)
{
	//Delete lists of filter IDs
	if (m_vidDecs != NULL)
	{
		for (int i=0; i < SUPPORTED_VID_FILTER_COUNT; i++)
		{
			if (m_vidDecs[i] != NULL)
			{
				delete m_vidDecs[i];
			}
		}

		delete [] m_vidDecs;
	}
	
	if (m_audDecs != NULL)
	{
		for (int i=0; i < SUPPORTED_AUD_FILTER_COUNT; i++)
		{
			if (m_audDecs[i] != NULL)
			{
				delete m_audDecs[i];
			}
		}

		delete [] m_audDecs;
	}

	if (m_capFilters != NULL)
	{
		for (int i=0; i < SUPPORTED_CAP_FILTER_COUNT; i++)
		{
			if (m_capFilters[i] != NULL)
			{
				// Capture devices share a GUID, so Moniker info is stored.
				// Because Moniker is a COM object, it must be released.
				SAFE_RELEASE(m_capFilters[i]->pFilterMoniker);
				
				delete m_capFilters[i];
			}
		}

		delete [] m_capFilters;
	}
}

HRESULT CodecManager::GetVidDecID (FilterIdent** vidDec, const VidFilter& index) const
{
	HRESULT hr = S_OK;

	*(vidDec) = m_vidDecs[index];

	// in the current implementation, there should be no failure resulting directly 
	// from the execture of this function call
	return hr;

}


HRESULT CodecManager::GetAudDecID(FilterIdent** audDec, const AudFilter& index) const
{
	HRESULT hr = S_OK;

	*(audDec) = m_audDecs[index];

	// in the current implementation, there should be no failure resulting directly 
	// from the execture of this function call
	return hr;

}


HRESULT CodecManager::GetCapID (FilterIdent** capFilter, const CapFilter& index) const
{
	HRESULT hr = S_OK;

	*(capFilter) = m_capFilters[index];

	// in the current implementation, there should be no failure resulting directly 
	// from the execture of this function call
	return hr;

}

HRESULT CodecManager::EnumerateSupportedCodecs()
{
	
	//***VIDEO CODECS***

	// ARCSOFT VIDEO DECODER
	if (IsFilterRegistered(L"\\CLSID\\{B793E9A8-C53E-4845-9DE9-C32326EACCAD}"))
	{
		m_vidDecs[VidFilter(ARCSOFT_VIDEO)] = new FilterIdent;
		m_vidDecs[VidFilter(ARCSOFT_VIDEO)]->friendlyName = "ArcSoft Video Decoder";
		m_vidDecs[VidFilter(ARCSOFT_VIDEO)]->curFilType = VIDEO_CODEC;
		m_vidDecs[VidFilter(ARCSOFT_VIDEO)]->filterCLSID = CLSID_ArcsoftVidDec;
	}

	// MS VIDEO DECODER
	if (IsFilterRegistered(L"\\CLSID\\{212690FB-83E5-4526-8FD7-74478B7939CD}"))
	{
		m_vidDecs[VidFilter(MS_VIDEO)] = new FilterIdent;
		m_vidDecs[VidFilter(MS_VIDEO)]->friendlyName = "Microsoft Video Decoder";
		m_vidDecs[VidFilter(MS_VIDEO)]->curFilType = VIDEO_CODEC;
		m_vidDecs[VidFilter(MS_VIDEO)]->filterCLSID = CLSID_MSVideoDecoder;
	}

	//***END VIDEO CODECS***

	//***AUDIO CODECS****

	// ARCSOFT AUDIO DECODER
	if (IsFilterRegistered(L"\\CLSID\\{1BFE85AC-1C05-457E-812A-E55D97FD25E5}"))
	{
		m_audDecs[AudFilter(ARCSOFT_AUDIO)] = new FilterIdent;
		m_audDecs[AudFilter(ARCSOFT_AUDIO)]->friendlyName = "ArcSoft Audio Decoder";
		m_audDecs[AudFilter(ARCSOFT_AUDIO)]->curFilType = AUDIO_CODEC;
		m_audDecs[AudFilter(ARCSOFT_AUDIO)]->filterCLSID = CLSID_ArcsoftAudDec;
	}

	// MS AUDIO DECODER
	if (IsFilterRegistered(L"\\CLSID\\{E1F1A0B8-BEEE-490D-BA7C-066C40B5E2B9}"))
	{
		m_audDecs[AudFilter(MS_AUDIO)] = new FilterIdent;
		m_audDecs[AudFilter(MS_AUDIO)]->friendlyName = "Microsoft Audio Decoder";
		m_audDecs[AudFilter(MS_AUDIO)]->curFilType = AUDIO_CODEC;
		m_audDecs[AudFilter(MS_AUDIO)]->filterCLSID = CLSID_MSAudioDecoder;
	}

	// FFDSHOW AUDIO DECODER
	if (IsFilterRegistered(L"\\CLSID\\{0F40E1E5-4F79-4988-B1A9-CC98794E6B55}"))
	{
		m_audDecs[AudFilter(FFDSHOW_AUDIO)] = new FilterIdent;
		m_audDecs[AudFilter(FFDSHOW_AUDIO)]->friendlyName = "FFDShow Audio Decoder";
		m_audDecs[AudFilter(FFDSHOW_AUDIO)]->curFilType = AUDIO_CODEC;
		m_audDecs[AudFilter(FFDSHOW_AUDIO)]->filterCLSID = CLSID_FFDShowAudio;
	}

	//***END AUDIO CODECS***

	return S_OK;
}

// Currently We only support the Hauppauge HDPVR (and likely will only support it)
HRESULT CodecManager::EnumerateCaptureDevice()
{
	HRESULT hr = S_OK;

	ICreateDevEnum *pSysDevEnum = NULL;
	IEnumMoniker *pEnumCat = NULL;
	IPropertyBag *pPropBag = NULL;
	
	
	VARIANT varName;
	BSTR sCrossbar = SysAllocString(L"Hauppauge HD PVR Crossbar");
	BSTR sCaptureDev = SysAllocString(L"Hauppauge HD PVR Capture Device");
	BSTR sEncoder = SysAllocString(L"Hauppauge HD PVR Encoder");

	BOOL bCBFound = FALSE;
	BOOL bCapFound = FALSE;
	BOOL bEncFound = FALSE;

	IMoniker *pMoniker = NULL;
    ULONG cFetched;
	
	// Enumerate the System Devices
	hr = CoCreateInstance(CLSID_SystemDeviceEnum, NULL, CLSCTX_INPROC_SERVER, IID_ICreateDevEnum, (void**)&pSysDevEnum);
	if(FAILED(hr))
		return hr;

	// Attempt to find the HD PVR CrossBar and add it to the Filter Graph
	hr = pSysDevEnum->CreateClassEnumerator(AM_KSCATEGORY_CROSSBAR, &pEnumCat, 0);
	if (hr==S_OK)   // Explicitly test for S_OK, because if the category is not found hr = S_FALSE, not E_FAIL (and will pass SUCCEEDED)
	{
		// Enumerate the monikers.	
    	while(pEnumCat->Next(1, &pMoniker, &cFetched) == S_OK && SUCCEEDED(hr))
    	{   
        	hr = pMoniker->BindToStorage(0, 0, IID_IPropertyBag, 
            	(void **)&pPropBag);
        	if (SUCCEEDED(hr))
        	{
            	// Find the Filter's friendly name           	
            	VariantInit(&varName);
            	hr = pPropBag->Read(L"FriendlyName", &varName, 0);
            	if (SUCCEEDED(hr))
            	{
					// because BSTRs are pointers, we can not directly compare.  use wchar_t* comparison function wcscmp
					// (BSTR is just a macro to a wchar_t, atleast in WIN32).  Note this only works because we BOTH
					// items are bstrings (we could not compare a wchar_t with a BSTR and expect proper functionality
					if (!wcscmp(varName.bstrVal, sCrossbar))  //wcscmp returns 0 of equal
					{
						// We have found the hauppauge crossbar.  Log the filter ID and save the moniker
						m_capFilters[CapFilter(HDPVR_CROSSBAR)] = new FilterIdent;
						m_capFilters[CapFilter(HDPVR_CROSSBAR)]->friendlyName = "HDPVR Crossbar";
						m_capFilters[CapFilter(HDPVR_CROSSBAR)]->curFilType = CAP_FILTER;
						m_capFilters[CapFilter(HDPVR_CROSSBAR)]->pFilterMoniker = pMoniker;
						m_capFilters[CapFilter(HDPVR_CROSSBAR)]->pFilterMoniker->AddRef();   // dont forget to add com reference

						bCBFound = TRUE;
					}

			
           		}

            	VariantClear(&varName);
                SAFE_RELEASE(pPropBag);

        	}
        	SAFE_RELEASE(pMoniker);

		}

		SAFE_RELEASE(pEnumCat);
	}
	
	// Add the capture filter
	if (SUCCEEDED(hr))
		hr = pSysDevEnum->CreateClassEnumerator(CLSID_VideoInputDeviceCategory, &pEnumCat, 0);

	if (hr==S_OK)
	{
		while(pEnumCat->Next(1, &pMoniker, &cFetched) == S_OK && SUCCEEDED(hr))
    	{   
        	hr = pMoniker->BindToStorage(0, 0, IID_IPropertyBag, 
            	(void **)&pPropBag);
        	if (SUCCEEDED(hr))
        	{
            	// Find the Filter's friendly name           	
            	VariantInit(&varName);
            	hr = pPropBag->Read(L"FriendlyName", &varName, 0);
            	if (SUCCEEDED(hr))
            	{
					// Check for the capture device
					if (!wcscmp(varName.bstrVal, sCaptureDev))
					{
						// We have found the hauppauge capture filter.  Log the filter ID and save the moniker
						m_capFilters[CapFilter(HDPVR_CAPTURE)] = new FilterIdent;
						m_capFilters[CapFilter(HDPVR_CAPTURE)]->friendlyName = "HDPVR Capture Device";
						m_capFilters[CapFilter(HDPVR_CAPTURE)]->curFilType = CAP_FILTER;
						m_capFilters[CapFilter(HDPVR_CAPTURE)]->pFilterMoniker = pMoniker;
						m_capFilters[CapFilter(HDPVR_CAPTURE)]->pFilterMoniker->AddRef();   // dont forget to add com reference
						bCapFound = TRUE;
					}
								
            	}
				
            	VariantClear(&varName);
                SAFE_RELEASE(pPropBag);

        	}

        	SAFE_RELEASE(pMoniker);

		}

		SAFE_RELEASE(pEnumCat);
		
	}

	// Now Add the Encoder filter
	if (SUCCEEDED(hr))
		hr = pSysDevEnum->CreateClassEnumerator(CLSID_Multiplexers, &pEnumCat, 0);

	if (hr==S_OK)
	{
		while(pEnumCat->Next(1, &pMoniker, &cFetched) == S_OK && SUCCEEDED(hr))
    	{   
        	hr = pMoniker->BindToStorage(0, 0, IID_IPropertyBag, 
            	(void **)&pPropBag);
        	if (SUCCEEDED(hr))
        	{
            	// Find the Filter's friendly name           	
            	VariantInit(&varName);
            	hr = pPropBag->Read(L"FriendlyName", &varName, 0);
            	if (SUCCEEDED(hr))
            	{		
					// Check for the encoder
					if (!wcscmp(varName.bstrVal, sEncoder))
					{
						// We have found the hauppauge encoder.  Log the filter ID and save the moniker
						m_capFilters[CapFilter(HDPVR_ENCODER)] = new FilterIdent;
						m_capFilters[CapFilter(HDPVR_ENCODER)]->friendlyName = "HDPVR Encoder";
						m_capFilters[CapFilter(HDPVR_ENCODER)]->curFilType = CAP_FILTER;
						m_capFilters[CapFilter(HDPVR_ENCODER)]->pFilterMoniker = pMoniker;
						m_capFilters[CapFilter(HDPVR_ENCODER)]->pFilterMoniker->AddRef();   // dont forget to add com reference
						bEncFound = TRUE;
					}
					
            	}
				
            	VariantClear(&varName);
                SAFE_RELEASE(pPropBag);

        	}

        	SAFE_RELEASE(pMoniker);

		}

		SAFE_RELEASE(pEnumCat);
		
	}

	if (SUCCEEDED(hr))
	{	
		// no critical error, but we didn't find the filters for our device
		if((!bCBFound || !bCapFound || !bEncFound))
		{
			hr = E_FAIL;
	
		}
	}

	SAFE_RELEASE(pSysDevEnum);

	return hr;
}

BOOL CodecManager::IsFilterRegistered(LPCWSTR keyLoc)
{
	LONG errStatus;
	HKEY key;

	errStatus = RegOpenKeyEx(HKEY_CLASSES_ROOT, keyLoc, 0,
		KEY_QUERY_VALUE, &key); 

	if (errStatus == ERROR_SUCCESS)
	{
		RegCloseKey(key);
		return TRUE;
	}
	else
		return FALSE;
}